using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Collections.Specialized;
using XTCast.Mp3;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
//using System.Runtime.Serialization.Formatters.Soap;
using System.Collections;
using System.Threading;
using System.Data;

namespace XTCast.Radio {
	
	public class MediaLibrary:IMediaLibrary,ICollection {

		public event FileChangedEventHandler FileChanged;

		public MediaLibrary(Station station, StationConfig sc) {
			station.MusicLibrary=this;
			this.station = station;
			this.sc = sc;			
		}

		public void Start() {
			thread=new Thread(new ThreadStart(Run));
			thread.Name = ToString();
			thread.IsBackground=true;
			thread.Priority = ThreadPriority.BelowNormal;
			thread.Start();
		}

		public override string ToString() {
			return String.Format("ML{0,13:G}", Station.ID);
		}

		virtual public StationConfig StationConfig {
			get {
				return sc;
			}
		}

		public MediaLibraryConfig Config {
			get{return sc.FileDJ.MediaLibrary;}
		}

		public Station Station {
			get{return station;}
		}

		public ICollection Files{
			get {
				if (_uniqFiles==null) return null;
				return _uniqFiles.Values;
			}
		}

		public bool Dirty {
			get{return _dirty;}
			set{_dirty=value;}
		}

		public bool NeedsRebuild {
			get{return _needsRebuild;}
			set{_needsRebuild=value; if (value) prog=0;}
		}

		public DateTime LastChange {
			get{return _lastChange;}
			set{_lastChange=value; NeedsRebuild = true;}
		}

		public virtual bool SyncTrackList() {
			lock (this) {
				if (Dirty) {
					Dirty = false;
					return true;
				}
				else
					return false;
			}
		}
		
		private void WriteTrackList(Hashtable uniqFile) {
			BinaryFormatter bf = new BinaryFormatter();
			FileStream fs = new IsolatedStorageFileStream(station.ID+".tracklist", FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
			try {
				bf.Serialize(fs,uniqFile); 
			} 
			finally {
				fs.Close();
			}
		}
		
		private Hashtable ReadTracks() {
			BinaryFormatter bf = new BinaryFormatter();
			FileStream fs = new IsolatedStorageFileStream(station.ID+".tracklist",FileMode.Open,FileAccess.Read,FileShare.ReadWrite);
			try {
				return (Hashtable)bf.Deserialize(fs);
			} 
			finally {
				fs.Close();
			}
		}
		
		private void ReadTracklist() {
			Hashtable uniqFiles;
			try {
				uniqFiles = ReadTracks();
			}
			catch (FileNotFoundException ex) 
			{
				uniqFiles = new System.Collections.Hashtable();
			}
			catch (Exception ex) 
			{
				Log.Error(this,ex);
				uniqFiles = new System.Collections.Hashtable();
			}
			lock (this) 
			{
				_uniqFiles = uniqFiles;
			}
		}

		private void BuildTrackList(bool quick) {
			int cap = 0;
			if (_uniqFiles != null) cap = _uniqFiles.Count;
			System.Collections.Hashtable uniqFiles = new System.Collections.Hashtable(cap,System.Collections.CaseInsensitiveHashCodeProvider.Default,System.Collections.CaseInsensitiveComparer.Default);
			
			foreach (string dir in Config.Include)
				AddDir(dir, uniqFiles, quick);
			
			_uniqFiles = uniqFiles;
			Dirty = true;
		}

		private void  AddDir(String path, System.Collections.Hashtable files, bool quick) {
			Log.Verbose(this, "Searching: " + path);
			try {
				foreach (String fname in Directory.GetFiles(path))
					try {
						if (MP3File.isMP3FileType(fname)) {
							MP3File f3 = new MP3File(fname,quick?MetaDataType.Filename:MetaDataType.ID3Tags);
							files[fname] = f3;
							if (_uniqFiles.Count<=0)
								prog = 50;
							else
								prog = (float)files.Count/((float)_uniqFiles.Count);
						}
					} 
					catch (Exception e) { Log.Verbose(e.ToString()); }
			} 
			catch (Exception e) { Log.Verbose(e.ToString()); }
			try {
				foreach (String dir in Directory.GetDirectories(path))
					try {
						if (FilterDir(dir))
							AddDir(dir,files,quick);
					} 
					catch (Exception e) { Log.Verbose(e.ToString()); }
			} 
			catch (Exception e) { Log.Verbose(e.ToString()); }
		}
		
		/// <summary>Determines whether a directory should be searched.
		/// By default it accepts all directories whose names don't start with "." or "(".
		/// You can override this to do fancier filtering.
		/// </summary>
		/// <returns>True to accept the directory, false to skip it. </returns>
		protected internal virtual bool FilterDir(String dir) {
			if (dir.StartsWith("RECYCLER")) return false;
			char first = dir[0];
			if (first == '.' || first == '(' || first == '_') return false;
			return Array.BinarySearch(Config.Exclude,dir)<0;
		}
		
//		public virtual MP3File[] GetAllFiles() {
//			MP3File[] _all = 
//			return this.Files.CopyTo(;
//		}

		public void Run() {
			Log.Verbose(this,"Starting...");
			try {
				ReadTracklist();
			} 
			catch (Exception ex) {
				Log.Error(this,ex);
			}
			while (Station.Run) {
				try {
					InitFsWatchers();
					if (NeedsRebuild && DateTime.Now.Subtract(LastChange)>TimeSpan.FromMinutes(2)) {
						bool quick = (Files.Count==0);
						Log.Info(this, String.Format("Started {0} media library",quick?"quick build":"re-building"));
						NeedsRebuild = quick;
						BuildTrackList(quick);
						_dt = GenerateDT();
						WriteTrackList(_uniqFiles);
						Log.Info(this, "Finished re-building media library");
					}
					Thread.Sleep(TimeSpan.FromMinutes(1));
				}
				catch (ThreadAbortException ex) {
				}
				catch (Exception ex) {
					Log.Error(this,ex);
				}
			}
			Log.Verbose(this,"Stopping...");
		}
		
		public Track GetNextTrack() {
			MP3File f = GetRandomFile();
			if (f==null) return null;
			f.ReadTags();
			Track track = new Track(f);
			Log.Verbose(this, "Selected track " + track.Name);
			return track;
		}

		public Track PeekNextTrack() {
			throw new InvalidOperationException();
		}
	

		public MP3File GetRandomFile() {
			lock (this) {
				if (Files==null || Files.Count==0) return null;
				while (true) {
					int index = random.Next(Files.Count);
					foreach (MP3File f in Files) {
						if (index<=0) return f;
						index--;
					}
				}
			}
		}

		DataTable GenerateDT() {
			DataTable dt = new DataTable("Mp3s");
			dt.Columns.Add("FileID",typeof(int));//.AutoIncrement=true;
			dt.Columns.Add("Title",typeof(String)).DefaultValue="";
			dt.Columns.Add("Artist",typeof(String)).DefaultValue="";
			dt.Columns.Add("Album",typeof(String)).DefaultValue="";
			dt.Columns.Add("Genre",typeof(String)).DefaultValue="";
			dt.Columns.Add("Mp3File",typeof(MP3File)).AllowDBNull=false;
			if (Count>0) {
				dt.BeginLoadData();
				foreach (MP3File f in this) {
					dt.LoadDataRow(new object[]{f.ID,f.Title,f.Artist,f.Album,f.Genre,f},true);
				}
				dt.EndLoadData();
			}
			return dt;
		}

		DataTable _dt;
		public DataTable DataTable {
			get {
				if (_dt==null) _dt = GenerateDT();
				return _dt;
			}
		}

		public System.Data.DataRow GetDataRow(int fileId) {
			DataRow[] rows = DataTable.Select("FileID=" + fileId);
			if (rows.Length==1) return rows[0];
			return null;
		}
	
		public MP3File GetFileByID(int fileId) {
			DataRow dr = GetDataRow(fileId);
			if (dr == null) return null;
			return (MP3File)dr["Mp3File"];
		}

		protected internal FileSystemWatcher[] fsWatcher;
		void InitFsWatchers () {
			if (fsWatcher==null) {
				string[] MusicDirs=Config.Include;
				fsWatcher = new FileSystemWatcher[MusicDirs.Length];
				for (int i=0;i<MusicDirs.Length;i++) {
					fsWatcher[i] = new FileSystemWatcher(MusicDirs[i],"*.mp3");
					fsWatcher[i].IncludeSubdirectories=true;
					fsWatcher[i].NotifyFilter=NotifyFilters.FileName|NotifyFilters.DirectoryName|NotifyFilters.LastWrite;
					fsWatcher[i].Changed+=new FileSystemEventHandler(MediaLibrary_Changed);
					fsWatcher[i].Created+=new FileSystemEventHandler(MediaLibrary_Changed);
					fsWatcher[i].Deleted+=new FileSystemEventHandler(MediaLibrary_Changed);
					fsWatcher[i].Renamed+=new RenamedEventHandler(MediaLibrary_Renamed);
					fsWatcher[i].EnableRaisingEvents=true;
				}
			}
		}

		protected void OnFileChanged(MP3File f, WatcherChangeTypes changeType) {
			if (FileChanged!=null) FileChanged(f,changeType);
			Log.Info(this, String.Format("{0} file: {1}",changeType,f));
		}

		private void MediaLibrary_Renamed(object sender, RenamedEventArgs e) {
			MP3File df = (MP3File)_uniqFiles[e.OldFullPath];
			if (df!=null) {
				Remove(df);
				OnFileChanged(df,WatcherChangeTypes.Deleted);
			}
			if (MP3File.isMP3FileType(e.FullPath)) {
				MP3File nf = new MP3File(e.FullPath);
				Add(nf);
				OnFileChanged(nf,WatcherChangeTypes.Created);
			}
		}
		private void MediaLibrary_Changed(object sender, FileSystemEventArgs e) {
			WatcherChangeTypes changeType = e.ChangeType;
			if (changeType==WatcherChangeTypes.Created && _uniqFiles[e.FullPath]==null) changeType=WatcherChangeTypes.Created;
			switch(changeType) {
				case(WatcherChangeTypes.Created): 
					if (MP3File.isMP3FileType(e.FullPath)) {
						MP3File crf = new MP3File(e.FullPath);
						Add(crf);
						OnFileChanged(crf,changeType);
					}
					break;
				case(WatcherChangeTypes.Changed):
					MP3File chf = (MP3File)_uniqFiles[e.FullPath];
					if (chf!=null && !chf.Dirty) {
						chf.Dirty=true;
						OnFileChanged(chf,WatcherChangeTypes.Changed);
					}
					break;
				case(WatcherChangeTypes.Deleted):
					MP3File df = (MP3File)_uniqFiles[e.FullPath];
					if (df!=null) {
						Remove(df);
						OnFileChanged(df,WatcherChangeTypes.Deleted);
					}
					break;
			}
		}
		#region ICollection Members

		public bool IsSynchronized {
			get {
				// TODO:  Add MediaLibrary.IsSynchronized getter implementation
				return false;
			}
		}

		public int Count {
			get {
				return _uniqFiles.Count;
			}
		}

		public void CopyTo(Array array, int index) {
			_uniqFiles.Values.CopyTo(array,index);
		}

		public object SyncRoot {
			get {
				return _uniqFiles.Values.SyncRoot;
			}
		}

		#endregion

		#region IEnumerable Members

		public IEnumerator GetEnumerator() {
			return _uniqFiles.Values.GetEnumerator();
		}

		#endregion

		public void Rescan() 
		{
			NeedsRebuild=true;
		}

		public float GetProgress()
		{
			return prog;
		}

		public void Add(MP3File value) {
			_uniqFiles.Add(value.FullName,value);
			Dirty = true;
			LastChange = DateTime.Now;
		}

		public void Remove(MP3File value) {
			_uniqFiles.Remove(value.FullName);
			Dirty = true;
			LastChange = DateTime.Now;
		}

		private Thread thread;
		protected internal Random random = new Random();
		protected internal StationConfig sc;
		protected internal Station station;
		protected internal System.Collections.Hashtable _uniqFiles;
		protected internal DateTime _lastChange;
		protected internal bool _needsRebuild = true;
		protected internal bool _dirty = false;
		protected internal float prog=0; //Progress of the scanning process
	}
}